Skip to content

Migrate Pester test suite from v4 to v5#164

Open
HeyItsGilbert wants to merge 22 commits intomasterfrom
pester-v5-migration
Open

Migrate Pester test suite from v4 to v5#164
HeyItsGilbert wants to merge 22 commits intomasterfrom
pester-v5-migration

Conversation

@HeyItsGilbert
Copy link
Copy Markdown
Member

@HeyItsGilbert HeyItsGilbert commented May 2, 2026

Summary

  • Test files: Fully migrated PSDepend.Tests.ps1 and PSModuleGallery.Type.Tests.ps1 to Pester 5 — all setup code moved into BeforeAll blocks with $script: scoped variables, assertion syntax updated (Should beShould -Be), Assert-MockCalled replaced with Should -Invoke, -ExclusiveFilter replaced with a two-assertion pattern, -Scope It removed, stub function definitions moved into BeforeAll so they survive the discovery→run phase boundary
  • Get-ClonedObject.ps1: Replaced BinaryFormatter (removed in .NET 7) with a recursive hashtable deep-clone — fixes 2 test failures on PS7
  • Test-PlatformSupport.ps1: Added a PS Core on Windows exemption so dependency types that declare Supports = 'windows' (FileDownload, FileSystem, Chocolatey) are no longer incorrectly skipped under PS7 Core on Windows — fixes 12 test failures

Result: 92 passed, 0 failed, 1 skipped (the 1 skip is a pre-existing PendingSkip tag, not a regression)

Test plan

  • Run Invoke-Pester ./Tests -Output Detailed locally — 92 passed, 0 failed
  • Verify CI passes on Windows, macOS, and Ubuntu runners (GitHub Actions matrix)

🤖 Generated with Claude Code

- Fix discovery/run phase separation: move all setup code into BeforeAll
  with $script: scoped variables; TestDrive access likewise moved
- Replace `Should be` with `Should -Be` throughout both test files
- Migrate Assert-MockCalled to Should -Invoke; remove -Scope It
- Replace -ExclusiveFilter (removed in v5) with two-assertion pattern
- Move stub function definitions into BeforeAll so they survive
  the discovery→run phase boundary (Chocolatey, Package types)
- Fix Get-ClonedObject: replace BinaryFormatter (removed in .NET 7)
  with a recursive hashtable clone
- Fix Test-PlatformSupport: allow PS Core on Windows to use dependency
  types that declare Supports = 'windows' (backwards compat)

Result: 92 passed, 0 failed, 1 skipped

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

Test Results

  3 files  +  3    9 suites  +9   1m 30s ⏱️ + 1m 30s
901 tests +901  837 ✅ +837  64 💤 +64  0 ❌ ±0 
903 runs  +903  837 ✅ +837  66 💤 +66  0 ❌ ±0 

Results for commit 2fcfaa4. ± Comparison against base commit 8fcca52.

♻️ This comment has been updated with latest results.

HeyItsGilbert and others added 18 commits May 1, 2026 22:33
* Added `Output/**` and `Tests/Output/**` to `.gitignore` to prevent unnecessary files from being tracked.
* Modified `build.ps1` to streamline the invocation of `Invoke-PSDepend` based on the `$Bootstrap` parameter.
* Set `RootDir` in `psakeFile.ps1` for Linux compatibility with explicit casing.
* Adjusted the version of `PowerShellBuild` from '0.7.2' to '0.6.1' to maintain compatibility with the current setup.
* Reformatted YAML for consistency in spacing.
* Separated Bootstrap and Test steps for clarity in the CI process.
* Changed `properties` to `Properties` for consistency.
* Removed unnecessary task definitions and streamlined the `Test` task.
* Adjusted comments for clarity regarding CI behavior.
PowerShellBuild 0.6.1 has no mechanism to override the Build task's
dependency chain from outside its psake file. 0.7.x introduced
$PSBBuildDependency which can be pre-set before -FromModule loads
the module, preventing BuildHelp (and GenerateMarkdown) from running.

GenerateMarkdown is not needed in the test pipeline and
Build-PSBuildMarkdown has a Remove-Module scope issue specific to
PSDepend that causes it to attempt removing 'platyPS' instead of
'PSDepend', failing because platyPS is required by PowerShellBuild.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…exts

Bare Set-StrictMode calls in Context block bodies run during Pester 5's
discovery phase, not test execution — the It blocks never actually ran
under strict mode. Wrapping in BeforeAll/AfterAll ensures strict mode
applies during execution and is cleaned up after each context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Four related fixes in one pass:

1. Prefix all outer Describe BeforeAll variables with $script: so they
   are visible in It blocks during test execution.
2. Prefix all inner Context BeforeAll variables with $script: and update
   the three parameter-validation It assertions to use $script: reads.
3. Add $helpParameters/$helpParameterNames to BeforeDiscovery so that
   -ForEach $helpParameterNames on the inner Context is non-empty at
   discovery time (BeforeAll runs too late for ForEach evaluation).
4. Replace undefined $parameterNames with $script:commandParameterNames.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mocks defined inside It blocks are scoped to that It and collapse when
it exits, making Should -Invoke assertions fragile and mixing arrangement
with assertion. Moved mocks to BeforeAll for the five affected contexts
(PSGalleryModule Imports, AddToPath-install, SkipPublisherCheck,
AllowPrerelease; PSGalleryNuget Imports, AddToPath-install) and added
-Scope Context to all Should -Invoke calls in those contexts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The two AddToPath-install contexts (PSGalleryModule and PSGalleryNuget)
restored $ENV:PSModulePath inside the It body. If the assertion before
that line failed, the env var would remain modified for the rest of the
run. Moved cleanup to AfterEach so it runs unconditionally. Also
switched to explicit $script:ExistingPSModulePath to match how the
variable is set in the outer BeforeAll.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Invoke-WebRequest had no timeout, causing the suite to hang on slow or
unreachable URLs. Added -TimeoutSec 10. Tagged the It blocks -Tag
'Acceptance' so offline CI runs can exclude them with -ExcludeTag
Acceptance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Git, FileDownload, and FileSystem Describe blocks all skip on non-Windows
via -Skip:\$nonWindows. The Chocolatey block only had -Tag 'WindowsOnly',
meaning it would still execute on Linux/macOS runners unless callers
explicitly excluded the tag. Aligned it with the other Windows-only blocks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The 'Installs Packages' Context was commented out pending Pester issue
#604 (mock parameter binding), which is resolved in Pester 5. Rewrote
it with BeforeAll, $script: variables, and -Scope Context.

Removed -Skip and the stale AppVeyor comment from 'Latest package
required' — the flakiness was caused by mock bleeding across contexts,
which Pester 5 scope isolation eliminates. Also moved all inline Mock
calls from It bodies in 'Same package version exists' and the latest
context into BeforeAll blocks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cover three scenarios missing from the existing success-path suite:
- -Tags filter returns only dependencies with matching tags
- -Tags with no match returns nothing
- -InputObject hashtable is parsed as PSGalleryModule by default

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…l-Dependency, Invoke-DependencyScript

Four previously untested public functions now have dedicated Describe
blocks:

- Get-PSDependType: returns typed objects, filters by wildcard, Supported
  is bool, throws on invalid path
- Get-PSDependScript: returns hashtable, all values are real paths, throws
  on invalid path
- Install-Dependency: pipeline from Get-Dependency calls Install-Module
- Invoke-DependencyScript: Command type returns output, non-existent action
  warns rather than throws, Test action with -Quiet returns bool

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add .EXAMPLE to Get-Dependency help (fixes help test failures)
- Fix .LINK URL typo PowerShellOr -> PowerShellOrg across all public functions
- Filter null URIs in Help.tests.ps1 to prevent FunctionInfo-as-Uri errors
  when .LINK entries are non-URL (function names, about pages)
- Strip -Force from PSBoundParameters before splatting to Invoke-DependencyScript,
  which has no -Force parameter
- Add -ModuleName PSDepend to all Mock/-Invoke assertions for commands called
  from within PSDepend dependency scripts (Install-Module, Save-Module, Get-Module,
  Find-Module, Import-Module, Get-PSRepository, Install-Package, Get-Package,
  Find-Package, Get-PackageSource, Get-NodeModule, Install-NodeModule, Test-Dotnet,
  Install-Dotnet, Get-DotnetVersion, Get-Command, Test-Path) so Pester v5 intercepts
  them at the correct module scope

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use @{} + $PSBoundParameters instead of .Clone() to copy PSBoundParameters
  into a mutable hashtable (PSBoundParametersDictionary has no Clone method)
- Inject stub functions for PackageManagement commands into PSDepend's module
  scope via & (Get-Module PSDepend) { ... } so -ModuleName PSDepend mocks
  can resolve them; remove now-redundant inline function redefinitions in
  individual Package contexts; add AfterAll cleanup
- Add -ModuleName PSDepend to all mocks in Git, FileDownload, PSGalleryNuget,
  and FileSystem Describe blocks — these are Windows-only sections where
  Invoke-ExternalCommand, Get-WebFile, Find-NugetPackage, Copy-Item,
  Import-LocalizedData, Import-Module, Test-Path, New-Item, Push-Location,
  Pop-Location, and Set-Location all resolve inside PSDepend's module scope
- Fix Npm: add -ModuleName PSDepend to Push-Location and Pop-Location in
  both Npm contexts; fix `return true` string bug to `return $true`

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Package: add [cmdletbinding()]param() to Get-PackageSource stub so Pester
  can generate a valid v5 mock proxy without ParameterBindingException
- Package: add Mock Get-PackageSource -ModuleName PSDepend to the three
  contexts that were hitting the real cmdlet (Same version, Latest, Test-Dependency)
- Npm: fix ParameterFilter from { $Target -eq 'Global' } to { $Global -eq $true };
  Npm.ps1 calls Get-NodeModule -Global (switch), not -Target 'Global'
- Chocolatey 'installs Chocolatey': add -ModuleName PSDepend to Get-Command
  and Invoke-WebRequest mocks and Should -Invoke assertions
- Chocolatey skip/install contexts: replace unmockable inline helpers
  (Get-ChocoInstalledPackage, Get-ChocoLatestPackage, Invoke-ChocoInstallPackage)
  with Invoke-ExternalCommand mocks; the inline functions are dot-sourced into
  local scope and shadow module-scope mocks, but Invoke-ExternalCommand is a
  PSDepend private module function that Pester can intercept; use ParameterFilter
  on --local-only (installed), list-no-local-only (latest), and upgrade (install)
  to distinguish the three call types and return appropriate CSV-formatted data

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without the script: scope qualifier, function definitions inside
& (Get-Module PSDepend) { function Foo { } } are created in a temporary
scope that vanishes when the scriptblock returns. The stubs were never
actually shadowing the real cmdlets.

Pester's mock proxy was being generated from the real Get-Package /
Install-Package / Find-Package / Get-PackageSource cmdlets (from
AnyPackage on Linux, PackageManagement elsewhere) — whose multi-parameter
-set definitions failed to bind our test splats with
ParameterBindingException: Parameter set cannot be resolved.

Using function script:Foo persists the stub in the module's script scope
so Get-Command -Module PSDepend resolves to the stub, and Pester generates
the proxy from the stub's simple param signature.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the repository’s PowerShell test suite and build pipeline to be compatible with Pester v5 / PS7+, while addressing PS7-specific runtime issues (deep cloning and platform support checks).

Changes:

  • Migrates PSDepend.Tests.ps1 and PSModuleGallery.Type.Tests.ps1 to Pester 5 syntax/scoping and updates mocking/assertions accordingly.
  • Replaces .NET BinaryFormatter usage with a recursive hashtable clone implementation and adjusts platform support logic for PS Core on Windows.
  • Updates build/CI configuration (PowerShellBuild version bump, psake/CI workflow adjustments) and adds a help validation test suite.

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
requirements.psd1 Bumps PowerShellBuild dependency version.
psakeFile.ps1 Updates PowerShellBuild task wiring and test root configuration.
build.ps1 Imports requirements during non-bootstrap runs.
Tests/PSModuleGallery.Type.Tests.ps1 Migrates integration tests to Pester 5 and updates mocks/assertions.
Tests/PSDepend.Tests.ps1 Migrates unit tests to Pester 5 and adds new unit coverage.
Tests/Help.tests.ps1 Adds command help validation tests.
PSDepend/Public/Test-Dependency.ps1 Formatting + help link cleanup.
PSDepend/Public/Invoke-PSDepend.ps1 Help link cleanup.
PSDepend/Public/Invoke-DependencyScript.ps1 Formatting + help link cleanup.
PSDepend/Public/Install-Dependency.ps1 Avoids splatting Force into Invoke-DependencyScript.
PSDepend/Public/Import-Dependency.ps1 Help link cleanup.
PSDepend/Public/Get-PSDependType.ps1 Help link cleanup.
PSDepend/Public/Get-PSDependScript.ps1 Formatting + minor style cleanup.
PSDepend/Public/Get-Dependency.ps1 Help updates + refactor formatting (includes a small comment typo).
PSDepend/Private/Test-PlatformSupport.ps1 Treats Supports='windows' as sufficient on Windows+Core.
PSDepend/Private/Get-ClonedObject.ps1 Replaces BinaryFormatter deep clone with recursive hashtable clone.
.gitignore Ignores build output folders.
.github/workflows/ci.yml Splits bootstrap and test steps in CI.
Comments suppressed due to low confidence (1)

psakeFile.ps1:34

  • psakeFile.ps1 no longer defines an Init task, but the repo still calls ./build.ps1 -Task Init (tests and CI). With only Default and Test tasks defined here, Invoke-Psake -TaskList Init will fail. Either reintroduce an Init task (e.g., Task Init -FromModule PowerShellBuild ...) or update all callers to use an existing task (such as Test/Default) that performs the required staging/build environment setup.
Task Default -Depends Test

Task Test -FromModule PowerShellBuild -MinimumVersion '0.7.3'


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/ci.yml
Comment thread Tests/PSDepend.Tests.ps1
Comment thread Tests/PSModuleGallery.Type.Tests.ps1
Comment thread Tests/Help.tests.ps1
Comment thread Tests/Help.tests.ps1
Comment thread PSDepend/Public/Get-Dependency.ps1 Outdated
Comment thread Tests/Help.tests.ps1
* Updated the path for uploading test results from `./Output/testResults.xml` to `./Tests/out/testResults.xml` to ensure proper artifact handling.
Comment thread PSDepend/Private/Get-ClonedObject.ps1
Comment thread .github/workflows/ci.yml
Comment thread Tests/PSDepend.Tests.ps1
* Fixed LicenseUri and ProjectUri in `PSDepend.psd1` and `about_PSDepend.help.txt` to point to the correct GitHub organization.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants